package com.dc.dbus.transport.netty.client;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map.Entry;

import com.dc.dbus.transport.netty.Log;
import com.dc.dbus.transport.netty.client.exception.CantFindActiveServiceException;
import com.dc.dbus.transport.netty.client.node.NodeInfo;
import com.dc.dbus.transport.netty.client.node.NodeLink;

public class Service {
	
	final NettyClientSideTransport nettyClientSideTransport;
	
	final String serviceName;
	
	volatile HashMap<String, ServiceRegion> routerMapRegion = new HashMap<>();
	
	public Service(NettyClientSideTransport nettyClientSideTransport,
			String serviceName) {
		this.nettyClientSideTransport = nettyClientSideTransport;
		this.serviceName = serviceName;
	}
	
	public synchronized void removeClient(String nodeName) {
		
		Log.nettyLinkLogger.info("Service.removeClient BEGIN: dealing node disconnect:" + nodeName);
		
		// 粗暴的循环所有节点
		
		HashMap<String, ServiceRegion> routerMapRegion = new HashMap<>(this.routerMapRegion);
		
		for (Entry<String, ServiceRegion> entry : routerMapRegion.entrySet()) {
			
			NodeLink[] nodeLinks = entry.getValue().nodeLinks;
			ArrayList<NodeLink> newNodeLinks = new ArrayList<>(nodeLinks.length);
			
			for (NodeLink n : nodeLinks) {
				if (!n.nodeName.equals(nodeName)) {
					newNodeLinks.add(n);
				} else {
					nettyClientSideTransport.needCleanNode(n);
				}
			}
			
			if (newNodeLinks.size() != nodeLinks.length) {
				nodeLinks = newNodeLinks.toArray(new NodeLink[newNodeLinks.size()]);
				routerMapRegion.put(entry.getKey(), new ServiceRegion(nodeName, entry.getValue().router, nodeLinks));
			}
		}
		
		this.routerMapRegion = routerMapRegion;

		Log.nettyLinkLogger.info("Service.removeClient END: dealing node disconnect:" + nodeName);
	}
	
	public synchronized void addClient(String nodeName, String[] routers, String[] hosts, int[] ports, NodeLink nodeLink) {

		String clientInfo = null;

		if (nodeLink.clients.length == 0) {

			clientInfo = "serviceName:" + serviceName
					+ ", clients(Channel).length="+ nodeLink.clients.length
					+ "<ERROR:can't link to node!!!>, nodeName" + nodeName
					+ ", routers"+ Arrays.toString(routers)
					+ ", ips"+ Arrays.toString(hosts)
					+ ", ports"+ Arrays.toString(ports);

			Log.nettyLinkLogger.warn("Service.addClient BEGIN: dealing node active, " + clientInfo);
		} else {

			clientInfo = "serviceName:" + serviceName
					+ ", clients(Channel).length="+ nodeLink.clients.length
					+ ", nodeName" + nodeName
					+ ", routers"+ Arrays.toString(routers)
					+ ", ips"+ Arrays.toString(hosts)
					+ ", ports"+ Arrays.toString(ports);

			Log.nettyLinkLogger.info("Service.addClient BEGIN: dealing node active, " + clientInfo);
		}

		HashMap<String, ServiceRegion> routerMapRegion = new HashMap<>(this.routerMapRegion);
		
		for (String router : routers) {
			
			ServiceRegion region = routerMapRegion.get(router);
			if (region == null) {
				region = new ServiceRegion(nodeName, router, new NodeLink[0]);
				routerMapRegion.put(router, region);
			}
			
			ArrayList<NodeLink> newNodeLinks = new ArrayList<>(region.nodeLinks.length + 1);
			for (NodeLink n : region.nodeLinks) {
				if (!n.nodeName.equals(nodeName)) {
					newNodeLinks.add(n);
				}
			}
			newNodeLinks.add(nodeLink);
			
			NodeLink[] newNodeLinkArray = newNodeLinks.toArray(new NodeLink[newNodeLinks.size()]);
			routerMapRegion.put(router, new ServiceRegion(nodeName, router, newNodeLinkArray));
		}
		
		this.routerMapRegion = routerMapRegion;

		Log.nettyLinkLogger.info("Service.addClient END: dealing node active, " + clientInfo);
	}
	
	public synchronized void addClient(String nodeName, String[] routers, String[] hosts, int[] ports) {
		
		NodeLink nodeLink = nettyClientSideTransport.getOrCreateNodeLink(nodeName, new NodeInfo(nodeName, nodeName, routers, hosts, ports));
		
		addClient(nodeName, routers, hosts, ports, nodeLink);
	}
	
	public synchronized void clientChange(String nodeName, String[] routers, String[] hosts, int[] ports) {

		String clientInfo = "serviceName:" + serviceName + ", nodeName" + nodeName
				+ ", routers"+ Arrays.toString(routers)
				+ ", ips"+ Arrays.toString(hosts)
				+ ", ports"+ Arrays.toString(ports);

		Log.nettyLinkLogger.info("Service.clientChange BEGIN: dealing node change, " + clientInfo);
		
		NodeLink oldNodeLink = nettyClientSideTransport.getNodeLink(nodeName);
		if (oldNodeLink == null) {
			addClient(nodeName, routers, hosts, ports);
			return;
		}
		
		// 只有routers才是允许动态改变的量（host,prot不允许变化）
		
		String[] oldRouters = oldNodeLink.routers;
		HashSet<String> oldRouterSet = new HashSet<>();
		for (int i=0; i<oldRouters.length; i++) {
			oldRouterSet.add(oldRouters[i]);
		}
		
		HashSet<String> newRouterSet = new HashSet<>();
		for (int i=0; i<routers.length; i++) {
			newRouterSet.add(routers[i]);
		}
		
		boolean routerMapRegionChange = false;
		HashMap<String, ServiceRegion> routerMapRegion = new HashMap<>(this.routerMapRegion);
		
		for (String oldRouter : oldRouterSet) {
			
			if (newRouterSet.remove(oldRouter)) {
				// 没有变化
			} else {
				
				// 删掉旧的数据分区上 的 该node的信息
				ServiceRegion region = routerMapRegion.get(oldRouter);
				if (region != null) {
					
					NodeLink[] nodeLinks = region.nodeLinks;
					ArrayList<NodeLink> newNodeLinks = new ArrayList<>(nodeLinks.length);
					
					for (NodeLink n : nodeLinks) {
						if (!n.nodeName.equals(nodeName)) {
							newNodeLinks.add(n);
						}
					}
					
					if (newNodeLinks.size() != nodeLinks.length) {
						nodeLinks = newNodeLinks.toArray(new NodeLink[newNodeLinks.size()]);
						routerMapRegion.put(oldRouter, new ServiceRegion(nodeName, oldRouter, nodeLinks));
						routerMapRegionChange = true;
					}
				}
				
				
			}
		}

		// newRouterSet中剩下的部分为需要新增的部分
		if (newRouterSet.size() > 0) {
			routerMapRegionChange = true;
		}
		for (String newRouter : newRouterSet) {
			
			ServiceRegion region = routerMapRegion.get(newRouter);
			if (region == null) {
				region = new ServiceRegion(nodeName, newRouter, new NodeLink[0]);
				routerMapRegion.put(newRouter, region);
			}
			
			ArrayList<NodeLink> newNodeLinks = new ArrayList<>(region.nodeLinks.length + 1);
			for (NodeLink n : region.nodeLinks) {
				if (!n.nodeName.equals(nodeName)) {
					newNodeLinks.add(n);
				}
			}
			newNodeLinks.add(oldNodeLink);
			
			NodeLink[] newNodeLinkArray = newNodeLinks.toArray(new NodeLink[newNodeLinks.size()]);
			routerMapRegion.put(newRouter, new ServiceRegion(nodeName, newRouter, newNodeLinkArray));
		}
		
		if (routers.length == 0) {
			nettyClientSideTransport.needCleanNode(oldNodeLink);
		}
		
		if (routerMapRegionChange) {
			this.routerMapRegion = routerMapRegion;
		}

		Log.nettyLinkLogger.info("Service.clientChange END: dealing node change, " + clientInfo);
	}
	
	public void writeAndFlush(String router, Object obj, int invokeSessionTimeout) {
		
		ServiceRegion region = routerMapRegion.get(router);
		if (region == null) {
			throw new CantFindActiveServiceException("not active server for service:" + serviceName + ", router:" + router);
		}
		
		region.writeAndFlush(router, obj, invokeSessionTimeout);
	}
	
}